#ifndef XG_POSTGRESQLCONNECT_CPP
#define XG_POSTGRESQLCONNECT_CPP
//////////////////////////////////////////////////////////////
#include "../PostgreSQLConnect.h"

#ifdef _MSC_VER
#pragma comment(lib, "libpq.lib")
#endif

PostgreSQLRowData::PostgreSQLRowData(PGresult* res)
{
	m_iRowNum = 0;
	m_bNull = true;
	this->res = res;
}
bool PostgreSQLRowData::isNull()
{
	return m_bNull;
}
int PostgreSQLRowData::getData(int index, char* data, int len)
{
	m_bNull = PQgetisnull(res, m_iRowNum, index) ? true : false;

	char* str = PQgetvalue(res, m_iRowNum, index);
	
	if (str == NULL) return 0;

	int sz = getDataLength(index);

	if (sz <= 0) return sz;
	if (len > sz) len = sz;

	memcpy(data, str, len);

	return len;
}
int PostgreSQLRowData::getDataLength(int index)
{
	return PQgetlength(res, m_iRowNum, index);
}
string PostgreSQLRowData::getString(int index)
{
	m_bNull = PQgetisnull(res, m_iRowNum, index) ? true : false;

	if (m_bNull) return stdx::EmptyString();

	return PQgetvalue(res, m_iRowNum, index);
}

PostgreSQLQueryResult::PostgreSQLQueryResult(PGresult* res, PGconn* conn)
{
	m_pConn = conn;
	iRowCount = res ? PQntuples(res) : -1;
	iCurIndex = iRowCount >= 0 ? (m_pSQLRes = res, 0) : (m_pSQLRes = NULL, -1);
}
PostgreSQLQueryResult::~PostgreSQLQueryResult()
{
	close();
}
void PostgreSQLQueryResult::close()
{
	if (m_pSQLRes)
	{
		iRowCount = iCurIndex = -1;
		PQclear(m_pSQLRes);
		m_pSQLRes = NULL;
	}

	m_pConn = NULL;
}
int PostgreSQLQueryResult::rows()
{
	return iRowCount;
}
int PostgreSQLQueryResult::cols()
{
	return PQnfields(m_pSQLRes);
}
string PostgreSQLQueryResult::getColumnName(int index)
{
	const char* msg = PQfname(m_pSQLRes, index);

	return msg ? msg : stdx::EmptyString();
}
sp<RowData> PostgreSQLQueryResult::next()
{
	if (iCurIndex < 0 || iCurIndex >= rows())
	{
		iCurIndex = -1;
		return NULL;
	}

	sp<PostgreSQLRowData> row = newsp<PostgreSQLRowData>(m_pSQLRes);

	row->m_iRowNum = iCurIndex++;

	return row;
}
bool PostgreSQLQueryResult::seek(int ofs)
{
	if (ofs >= rows()) return false;

	iCurIndex = ofs;

	return true;
}
bool PostgreSQLQueryResult::getColumnData(ColumnData& data, int index)
{
	data.scale = 0;
	data.type = PQftype(m_pSQLRes, index);
	data.size = PQfsize(m_pSQLRes, index);
	data.nullable = PQfmod(m_pSQLRes, index) == -1;

	strncpy(data.name, PQfname(m_pSQLRes, index), sizeof(data.name));
	data.name[sizeof(data.name) - 1] = 0;

	if (data.type == 20 || data.type == 23)
	{
		data.type = DBData_Integer;
	}
	else if (data.type == 700 || data.type == 1700)
	{
		data.type = DBData_Float;
	}
	else if (data.type == 1042 || data.type == 1043)
	{
		data.type = DBData_String;
	}
	else if (data.type == 1114)
	{
		data.type = DBData_DateTime;
	}
	else
	{
		data.type = DBData_Other;
	}

	return true;
}
int PostgreSQLQueryResult::getErrorCode()
{
	return PQresultStatus(m_pSQLRes);
}
string PostgreSQLQueryResult::getErrorString()
{
	char* err = PQresultErrorMessage(m_pSQLRes);
	
	if (err == NULL) return stdx::EmptyString();

	string msg = err;

	PQfreemem(err);

	return msg;
}



PostgreSQLConnect::PostgreSQLConnect()
{
	m_pConn = NULL;
}
PostgreSQLConnect::~PostgreSQLConnect()
{
	close();
}
const char* PostgreSQLConnect::getSystemName()
{
	return "PostgreSQL";
}
bool PostgreSQLConnect::commit()
{
	PGresult* res = PQexec(m_pConn, "commit");

	if (res == NULL) return false;

	PQclear(res);
	
	return true;
}
bool PostgreSQLConnect::rollback()
{
	PGresult* res = PQexec(m_pConn, "rollback");

	if (res == NULL) return false;

	PQclear(res);
	
	return true;
}
bool PostgreSQLConnect::begin(bool commited)
{
	if (commited) CHECK_FALSE_RETURN(commit());
	
	PGresult* res = PQexec(m_pConn, "begin");

	if (res == NULL) return false;

	PQclear(res);
	
	return true;
}
bool PostgreSQLConnect::setCharset(const string& charset)
{
	return PQsetClientEncoding(m_pConn, charset.c_str()) == 0;
}
bool PostgreSQLConnect::connect(const string& host, int port, const string& name, const string& user, const string& password)
{
	close();

	char str[512];
	const char* fmt = "host=%s dbname=%s user=%s password=%s port=%d";

	sprintf(str, fmt, host.c_str(), name.c_str(), user.c_str(), password.c_str(), port);

	CHECK_FALSE_RETURN(m_pConn = PQconnectdb(str));

	setCharset(stdx::replace(GetLocaleCharset(), "-", ""));

	return true;
}
void PostgreSQLConnect::close()
{
	if (m_pConn)
	{
		PQfinish(m_pConn);
		m_pConn = NULL;
	}
}
int PostgreSQLConnect::getPrimaryKeys(vector<string>& vec, const string& tabname)
{
	string sql = "SELECT pg_constraint.conname AS pk_name,pg_attribute.attname AS colname,pg_type.typname AS typename FROM pg_constraint INNER JOIN pg_class ON pg_constraint.conrelid=pg_class.oid INNER JOIN pg_attribute ON pg_attribute.attrelid=pg_class.oid AND pg_attribute.attnum=ANY(pg_constraint.conkey) INNER JOIN pg_type ON pg_type.oid=pg_attribute.atttypid WHERE pg_class.relname='" + tabname + "' AND pg_constraint.contype='p'";

	sp<RowData> row;
	sp<QueryResult> rs = query(sql);
	
	if (!rs) return -1;

	vec.clear();

	while (row = rs->next()) vec.push_back(row->getString(1));

	if (vec.empty())
	{
		sql = "SELECT * FROM " + tabname + " WHERE 1=0";

		if (!(rs = query(sql)) return -1;

		string name;
		int cols = rs->cols();

		for (int i = 0; i < cols; i++)
		{
			if ((name = rs->getColumnName(i)).empty()) return -1;

			vec.push_back(name);
		}
	}

	return vec.size();
}
int PostgreSQLConnect::getTables(vector<string>& vec)
{
	const char* sql = "SELECT tablename FROM pg_tables WHERE schemaname = \'public\'";

	sp<QueryResult> rs = query(sql);

	vec.clear();
	
	if (rs)
	{
		sp<RowData> row;

		while (row = rs->next()) vec.push_back(row->getString(0));
	}

	return vec.size();
}
int PostgreSQLConnect::bind(void* stmt, const vector<DBData*>& vec)
{
	return 0;
}
sp<QueryResult> PostgreSQLConnect::query(const string& sqlcmd)
{
	PGresult* res = PQexec(m_pConn, sqlcmd.c_str());
	
	if (res == NULL)
	{
		updateStatus(SQLRtn_Error, sqlcmd);

		return NULL;
	}

	sp<PostgreSQLQueryResult> rs = newsp<PostgreSQLQueryResult>(res, m_pConn);
	
	updateStatus(rs->rows(), sqlcmd);

	return rs;
}
sp<QueryResult> PostgreSQLConnect::query(const string& sqlcmd, const vector<DBData*>& vec)
{
	string val;
	string str;
	const char* sql = sqlcmd.c_str();

	if (vec.empty()) return query(sqlcmd);

	const char* p = sql;
	const char* q = NULL;
	string bindstr = getBindString();
	size_t count = GetStringCount(sql, bindstr.c_str());

	if (count > vec.size()) count = vec.size();

	if (count > 0)
	{
		for (size_t i = 0, sz = 0; i < count; i++)
		{
			assert(vec[i]->type() == DBData_Blob || vec[i]->type() == DBData_String);

			char* res = NULL;

			if (vec[i]->isNull())
			{
				val = "\'\'";
			}
			else if (vec[i]->type() == DBData_Blob)
			{
				DBBlob* data = (DBBlob*)(vec[i]);

				res = (char*)PQescapeByteaConn(m_pConn, data->val().ptr(), data->size(), &sz);
				
				if (res == NULL)
				{
					updateStatus(XG_SYSERR, sqlcmd, vec);

					return NULL;
				}

				val = "E\'";
				val += res;
				val += "\'::bytea";
				
				PQfreemem((void*)(res));
			}
			else
			{
				val = vec[i]->toString();
				res = new char[val.length() * 2 + 1];
				sz = PQescapeStringConn(m_pConn, res, val.c_str(), val.length(), NULL);

				if (sz < 0)
				{
					updateStatus(XG_SYSERR, sqlcmd, vec);

					delete[] res;
					
					return NULL;
				}
				
				val = "\'";
				val += res;
				val += "\'";

				delete[] res;
			}

			q = strstr(p, bindstr.c_str());
			str += string(p, q);
			str += val;

			p = q + bindstr.length();
		}

		str += p;
		sql = str.c_str();
	}	

	return query(sql);
}
int PostgreSQLConnect::execute(const string& sqlcmd)
{
	int rows = 0;
	PGresult* res = PQexec(m_pConn, sqlcmd.c_str());

	if (res == NULL)
	{
		updateStatus(SQLRtn_Error, sqlcmd);

		return SQLRtn_Error;
	}

	char* str = PQcmdTuples(res);
	
	if (str == NULL || *str == 0)
	{
		updateStatus(SQLRtn_Error, sqlcmd);

		if (PQresultStatus(res) == PGRES_FATAL_ERROR)
		{
			PQclear(res);

			return SQLRtn_Duplicate;
		}

		PQclear(res);

		return SQLRtn_Error;
	}

	rows = stdx::atoi(str);

	updateStatus(rows, sqlcmd);
	
	PQclear(res);

	return rows;
}
int PostgreSQLConnect::execute(const string& sqlcmd, const vector<DBData*>& vec)
{
	string val;
	string str;
	const char* sql = sqlcmd.c_str();

	if (vec.empty()) return execute(sqlcmd);

	const char* p = sql;
	const char* q = NULL;
	string bindstr = getBindString();
	size_t count = GetStringCount(sql, bindstr.c_str());

	if (count > vec.size()) count = vec.size();

	if (count > 0)
	{
		for (size_t i = 0, sz = 0; i < count; i++)
		{
			assert(vec[i]->type() == DBData_Blob || vec[i]->type() == DBData_String);

			char* res = NULL;

			if (vec[i]->isNull())
			{
				val = "\'\'";
			}
			else if (vec[i]->type() == DBData_Blob)
			{
				DBBlob* data = (DBBlob*)(vec[i]);

				res = (char*)PQescapeByteaConn(m_pConn, data->val().ptr(), data->size(), &sz);
				
				if (res == NULL)
				{
					updateStatus(XG_SYSERR, sqlcmd, vec);

					return SQLRtn_System;
				}

				val = "E\'"; val += res; val += "\'::bytea";
				
				PQfreemem((void*)(res));
			}
			else
			{
				DBString* data = (DBString*)(vec[i]);

				sz = data->val().length();
				res = new char[sz * 2 + 1];
				sz = PQescapeStringConn(m_pConn, res, data->val().c_str(), sz, NULL);
				
				if (sz < 0)
				{
					updateStatus(XG_SYSERR, sqlcmd, vec);

					delete[] res;

					return SQLRtn_System;
				}

				val = "\'"; val += res; val += "\'";
				
				delete[] res;
			}

			q = strstr(p, bindstr.c_str());
			str += string(p, q);
			str += val;

			p = q + bindstr.length();
		}

		str += p;
		sql = str.c_str();
	}	

	return execute(sql);
}
int PostgreSQLConnect::getErrorCode()
{
	return m_pConn ? PQstatus(m_pConn) : XG_ERROR;
}
string PostgreSQLConnect::getErrorString()
{
	if (m_pConn == NULL) return stdx::EmptyString();

	char* err = PQerrorMessage(m_pConn);
	
	if (err == NULL) return stdx::EmptyString();

	string msg = err;

	PQfreemem(err);

	return msg;
}
bool PostgreSQLConnect::Setup()
{
	return true;
}
//////////////////////////////////////////////////////////////
#endif